﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using System.Linq.Expressions;

namespace DotNETX.CommonWcfClient
{
    public class CommonClient<TChannel> : ClientBase<TChannel>, IDisposable where TChannel : class
    {
        private TChannel _proxy = null;

        #region Constructors
        public CommonClient() { }

        public CommonClient(ServiceEndpoint endpoint) : base(endpoint) { }

        public CommonClient(string endpointConfigurationName) : base(endpointConfigurationName) { }

        public CommonClient(Binding binding, EndpointAddress remoteAddress) : base(binding, remoteAddress) { }

        public CommonClient(string endpointConfigurationName, EndpointAddress remoteAddress) : base(endpointConfigurationName, remoteAddress) { }

        public CommonClient(string endpointConfigurationName, string remoteAddress) : base(endpointConfigurationName, remoteAddress) { }

        public CommonClient(InstanceContext callbackInstance) : base(callbackInstance) { }

        public CommonClient(InstanceContext callbackInstance, ServiceEndpoint endpoint) : base(callbackInstance, endpoint) { }

        public CommonClient(InstanceContext callbackInstance, string endpointConfigurationName) : base(callbackInstance, endpointConfigurationName) { }

        public CommonClient(InstanceContext callbackInstance, Binding binding, EndpointAddress remoteAddress) : base(callbackInstance, binding, remoteAddress) { }

        public CommonClient(InstanceContext callbackInstance, string endpointConfigurationName, EndpointAddress remoteAddress) : base(callbackInstance, endpointConfigurationName, remoteAddress) { }

        public CommonClient(InstanceContext callbackInstance, string endpointConfigurationName, string remoteAddress) : base(callbackInstance, endpointConfigurationName, remoteAddress) { }

        #endregion

        /// <summary>
        /// 获取服务代理接口
        /// 
        /// 默认设置启用代理闲置超时机制（IsCheckTimeoutEnabled = true），默认超时限制为5分钟（MaxAliveTrustedTimeSpan = 1000 * 60 * 5）（单位：毫秒）
        /// 设置连通性测试方法，SetCheckAliveMethod，设置成功后在代理闲置之后将会尝试连通性测试先，避免重新生成服务代理借口。同时也是启用心跳保持机制的先决条件。
        /// 默认设置不启用心跳保持，当设置了连通性测试方法并设置了IsKeepAliveEnabled属性为True时，则启用心跳保持，客户端实例将会在闲置超时时限到达时自动调用SetCheckAliveMethod设置的连通性方法，并重置计时器。
        /// </summary>
        public virtual TChannel Proxy
        {
            get
            {
                if (_proxy == null)
                {
                    CreateChannel();
                }
                else
                {
                    if (IsCheckTimeoutEnabled && CheckTimeout())
                    {
                        if (IsCheckAliveMethodSeted)
                        {
                            if (!CallCheckAliveMethod()) CreateChannel();
                        }
                        else
                        {
                            try
                            {
                                IClientChannel c = (_proxy as IClientChannel);
                                if (c != null)
                                {
                                    c.Abort();
                                    c.Close();
                                }
                            }
                            finally
                            {
                                CreateChannel();
                            }
                        }
                    }
                }
                if (null != _proxy && _ka_IsKeepAliveEnabled)
                {
                    StopKeepAlive();
                    StartKeepAlive();
                }
                return _proxy;
            }
        }

        protected virtual TChannel CreateChannel()
        {
            try
            {
                _proxy = base.CreateChannel();
                IClientChannel ch = _proxy as IClientChannel;
                ch.Closed += (s, e) => { if (ProxyClosed != null)ProxyClosed(this, e); };
                ch.Closing += (s, e) => { if (ProxyClosing != null)ProxyClosing(this, e); };
                ch.Faulted += (s, e) => { if (ProxyFaulted != null)ProxyFaulted(this, e); if (IsAutoRecreateChannel)CreateChannel(); };
                ch.Opened += (s, e) => { if (ProxyOpened != null)ProxyOpened(this, e); };
                ch.Opening += (s, e) => { if (ProxyOpening != null)ProxyOpening(this, e); };

                _ka_LastReferenceTime = 0;
            }
            catch
            {
                _proxy = null;
            }
            return _proxy;
        }

        #region KeepAlive

        protected bool _ka_IsCheckTimeoutEnabled = true;
        protected bool _ka_IsAutoRecreateChannel = true;
        protected double _ka_LastReferenceTime = 0;
        protected double _ka_MaxAliveTrustedTimeSpan = 1000 * 60 * 5;

        protected MethodInfo _ka_CheckAliveMethodInfo = null;
        protected object[] _ka_CheckAliveMethodArgs = null;
        protected int _ka_ContinueErrorCount = 0;

        protected bool _ka_IsKeepAliveEnabled = false;
        protected System.Timers.Timer _ka_Timer = null;

        /// <summary>
        /// 获取或设置连通性最长可信时间，同时也是心跳的参考时间间隔 默认5分钟
        /// </summary>
        public virtual double MaxAliveTrustedTimeSpan
        {
            get { return _ka_MaxAliveTrustedTimeSpan; }
            set { _ka_MaxAliveTrustedTimeSpan = value; }
        }

        /// <summary>
        /// 获取或设置是否启用心跳保持连接，默认False
        /// </summary>
        public virtual bool IsKeepAliveEnabled
        {
            get { return _ka_IsKeepAliveEnabled; }
            set
            {
                if (value == _ka_IsKeepAliveEnabled) return;
                _ka_IsKeepAliveEnabled = value;
                if (null == _proxy) return;
                if (value)
                {
                    if (!StartKeepAlive()) new Exception("终止心跳失败");
                }
                else
                {
                    if (!StopKeepAlive()) new Exception("终止心跳失败");
                }
            }
        }

        /// <summary>
        /// 获取或设置是否在调用Proxy时验证上次调用时间是否超过连通性最长可信时间，默认True
        /// </summary>
        public virtual bool IsCheckTimeoutEnabled
        {
            get { return _ka_IsCheckTimeoutEnabled; }
            set { _ka_IsCheckTimeoutEnabled = true; }
        }

        /// <summary>
        /// 获取或设置服务代理在发生异常之后是否自动重新创建通道，默认True
        /// </summary>
        public virtual bool IsAutoRecreateChannel
        {
            get { return _ka_IsAutoRecreateChannel; }
            set
            {
                _ka_IsAutoRecreateChannel = value;
            }
        }

        /// <summary>
        /// 获取通道测试方法是否设置
        /// </summary>
        protected virtual bool IsCheckAliveMethodSeted
        {
            get { return null != _ka_CheckAliveMethodInfo; }
        }

        /// <summary>
        /// 设置通道测试方法，用于检测服务连通性
        /// </summary>
        /// <param name="methodName">服务代理借口中的连通性测试方法名称</param>
        /// <param name="args">默认参数实例</param>
        /// <returns></returns>
        public virtual bool SetCheckAliveMethod(string methodName, params object[] args)
        {
            _ka_CheckAliveMethodInfo = typeof(TChannel).GetMethod(methodName);
            if (null == _ka_CheckAliveMethodInfo)
            {
                return false;
            }
            else
            {
                _ka_CheckAliveMethodArgs = args;
                return true;
            }
        }

        /// <summary>
        /// 检查是否超时
        /// </summary>
        /// <returns></returns>
        protected virtual bool CheckTimeout()
        {
            try
            {
                bool b_timeout = true;
                double now = (DateTime.Now - DateTime.MinValue).TotalMilliseconds;
                if (_ka_MaxAliveTrustedTimeSpan > 0)
                {
                    if (_ka_LastReferenceTime == 0)
                    {
                        b_timeout = false;
                    }
                    else
                    {
                        b_timeout = now - _ka_LastReferenceTime > _ka_MaxAliveTrustedTimeSpan;
                    }
                }
                else
                {
                    b_timeout = false;
                }
                _ka_LastReferenceTime = now;

                return b_timeout;
            }
            catch
            {
                return true;
            }
        }

        /// <summary>
        /// 调用测试连通性方法
        /// </summary>
        /// <returns></returns>
        protected virtual bool CallCheckAliveMethod()
        {
            if (_ka_CheckAliveMethodInfo == null) return false;
            else
            {
                try
                {
                    _ka_CheckAliveMethodInfo.Invoke(this._proxy, _ka_CheckAliveMethodArgs);
                    return true;
                }
                catch
                {

                    return false;
                }
            }
        }

        /// <summary>
        /// 开始心跳
        /// </summary>
        protected virtual bool StartKeepAlive()
        {
            if (0 >= MaxAliveTrustedTimeSpan || !IsCheckAliveMethodSeted) return false;
            if (null == _ka_Timer)
            {
                _ka_Timer = CreateTimer();
            }
            if (_ka_Timer.Enabled) return true;
            double intvl = (MaxAliveTrustedTimeSpan < 11000)
                                ? MaxAliveTrustedTimeSpan : MaxAliveTrustedTimeSpan - 100;
            if (intvl != _ka_Timer.Interval) _ka_Timer.Interval = intvl;
            try
            {
                _ka_Timer.Start();
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// 停止心跳
        /// </summary>
        /// <returns></returns>
        protected virtual bool StopKeepAlive()
        {
            if (null == _ka_Timer || !_ka_Timer.Enabled) return true;
            try
            {
                _ka_Timer.Stop();
                return true;
            }
            catch
            {
                return false;
            }
        }

        private System.Timers.Timer CreateTimer()
        {
            System.Timers.Timer _Timer = new System.Timers.Timer();
            _Timer.Elapsed += TimerElapsed;
            _Timer.AutoReset = true;
            return _Timer;
        }

        private void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (_proxy == null) return;
            if (CallCheckAliveMethod())
            {
                _ka_LastReferenceTime = (DateTime.Now - DateTime.MinValue).TotalMilliseconds;
                _ka_ContinueErrorCount = 0;
            }
            else
            {
                _ka_ContinueErrorCount++;
                if (_ka_ContinueErrorCount >= 3) _ka_Timer.Stop();
            }
        }

        #endregion

        //
        // 摘要:
        //     当通信对象转换到已关闭状态时发生。
        public event EventHandler ProxyClosed;
        //
        // 摘要:
        //     当通信对象转换到正在关闭状态时发生。
        public event EventHandler ProxyClosing;
        //
        // 摘要:
        //     在通信对象转换到出错状态时发生。
        public event EventHandler ProxyFaulted;
        //
        // 摘要:
        //     当通信对象转换到已打开状态时发生。
        public event EventHandler ProxyOpened;
        //
        // 摘要:
        //     当通信对象转换到正在打开状态时发生。
        public event EventHandler ProxyOpening;

        #region IDisposable

        public void Dispose()
        {
            AbortClose();
        }

        public void AbortClose()
        {
            //avoid the CommunicationObjectFaultedException 
            if (this.State != CommunicationState.Closed) this.Abort();
            //safe to close the client
            this.Close();
        }

        #endregion
    }
}
